#include "EFrameListener.h"
#include "EApp.h"
#include "EGame.h"
#include "ECharacter.h"
#include "eCPP.h"

namespace engine {

//------------------------------------------------------------
EFrameListener::EFrameListener(EApp& app, RenderWindow* pWin, Camera* pCam, bool bufferedKeys, bool bufferedMouse,
         bool bufferedJoy ) :
  m_app(app), m_pCamera(pCam), m_TranslateVector(Vector3::ZERO), m_Window(pWin), m_StatsOn(true), m_NumScreenShots(0),
  m_MoveScale(0.0f), m_RotScale(0.0f), m_TimeUntilNextToggle(0), m_Filtering(TFO_BILINEAR),
  m_Aniso(1), m_SceneDetailIndex(0), m_MoveSpeed(100), m_RotateSpeed(36), m_DebugOverlay(0),
  m_InputManager(0), m_pMouse(0), m_pKeyboard(0), m_pJoy(0)
{
  using namespace OIS;

  //m_DebugOverlay = OverlayManager::getSingleton().getByName("Core/DebugOverlay");

  LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
  ParamList pl;
  size_t windowHnd = 0;
  std::ostringstream windowHndStr;

  pWin->getCustomAttribute("WINDOW", &windowHnd);
  windowHndStr << windowHnd;
  pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

  m_InputManager = InputManager::createInputSystem( pl );

  //Create all devices (We only catch joystick exceptions here, as, most people have Key/Mouse)
  m_pKeyboard  = static_cast<Keyboard*>(m_InputManager->createInputObject( OISKeyboard, bufferedKeys ));
  m_pMouse     = static_cast<Mouse*>(m_InputManager->createInputObject( OISMouse, bufferedMouse ));
  try {
    m_pJoy = static_cast<JoyStick*>(m_InputManager->createInputObject( OISJoyStick, bufferedJoy ));
  }
  catch(...) {
    m_pJoy = NULL;
  }

  //Set initial mouse clipping size
  windowResized(m_Window);

  //showDebugOverlay(false);

  //Register as a Window listener
  WindowEventUtilities::addWindowEventListener(m_Window, this);
}

//------------------------------------------------------------
EFrameListener::~EFrameListener()
{
  //Remove ourself as a Window listener
  WindowEventUtilities::removeWindowEventListener(m_Window, this);
  windowClosed(m_Window);
}

//------------------------------------------------------------
void EFrameListener::updateStats()
{
  /*static String currFps   = "Current FPS: ";
  static String sCameraPos = "Camera Pos : ( ";
  static String sCameraOri = "Camera Or : ( ";
  static String tris      = "Triangle Count: ";
  static String batches   = "Batch Count: ";

  // update stats when necessary
  try {
    OverlayElement* guiCamPos   = OverlayManager::getSingleton().getOverlayElement("Core/AverageFps");
    OverlayElement* guiCurr     = OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");
    OverlayElement* guiCamOri   = OverlayManager::getSingleton().getOverlayElement("Core/BestFps");
    OverlayElement* guiWorst    = OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");

    const RenderTarget::FrameStats& stats = m_Window->getStatistics();
    Vector3     vCamPos = m_pCamera->getPosition();
    Quaternion  qCamOri = m_pCamera->getOrientation();
    guiCamPos->setCaption(sCameraPos + StringConverter::toString(vCamPos.x)
     + ", " + StringConverter::toString(vCamPos.y)
     + ", " + StringConverter::toString(vCamPos.z) + " )" );
    guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));
    guiCamOri->setCaption(sCameraOri + StringConverter::toString(qCamOri.x)
      + ", " + StringConverter::toString(qCamOri.y)
      + ", " + StringConverter::toString(qCamOri.z)
      + ", " + StringConverter::toString(qCamOri.w) + " )"  );

    OverlayElement* guiTris = OverlayManager::getSingleton().getOverlayElement("Core/NumTris");
    guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));

    OverlayElement* guiBatches = OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");
    guiBatches->setCaption(batches + StringConverter::toString(stats.batchCount));

    OverlayElement* guiDbg = OverlayManager::getSingleton().getOverlayElement("Core/DebugText");
    guiDbg->setCaption(m_DebugText);
  }
  catch(...) {  }*/
}

//------------------------------------------------------------
void EFrameListener::windowResized(RenderWindow* pRw)
{
  unsigned int  iWidth, iHeight, iDepth;
  int           iLeft, iTop;
  pRw->getMetrics(iWidth, iHeight, iDepth, iLeft, iTop);

  const OIS::MouseState &ms = m_pMouse->getMouseState();
  ms.width  = iWidth;
  ms.height = iHeight;
}

//------------------------------------------------------------
void EFrameListener::windowClosed(RenderWindow* pRw)
{
  //Only close for window that created OIS (the main window in these demos)
  if( pRw == m_Window )
  {
    if( m_InputManager )
    {
      m_InputManager->destroyInputObject( m_pMouse );
      m_InputManager->destroyInputObject( m_pKeyboard );
      m_InputManager->destroyInputObject( m_pJoy );

      OIS::InputManager::destroyInputSystem(m_InputManager);
      m_InputManager = 0;
    }
  }
}

//------------------------------------------------------------
bool EFrameListener::processUnbufferedKeyInput( const FrameEvent& evt )
{
  using namespace OIS;

  if(m_pKeyboard->isKeyDown(KC_A))
    m_TranslateVector.x = -m_MoveScale;	// Move camera left

  if(m_pKeyboard->isKeyDown(KC_D))
    m_TranslateVector.x = m_MoveScale;	// Move camera RIGHT

  if(m_pKeyboard->isKeyDown(KC_UP) || m_pKeyboard->isKeyDown(KC_W) )
    m_TranslateVector.z = -m_MoveScale;	// Move camera forward

  if(m_pKeyboard->isKeyDown(KC_DOWN) || m_pKeyboard->isKeyDown(KC_S) )
    m_TranslateVector.z = m_MoveScale;	// Move camera backward

  if(m_pKeyboard->isKeyDown(KC_PGUP))
    m_TranslateVector.y = m_MoveScale;	// Move camera up

  if(m_pKeyboard->isKeyDown(KC_PGDOWN))
    m_TranslateVector.y = -m_MoveScale;	// Move camera down

  if(m_pKeyboard->isKeyDown(KC_RIGHT))
    m_pCamera->yaw(-m_RotScale);

  if(m_pKeyboard->isKeyDown(KC_LEFT))
    m_pCamera->yaw(m_RotScale);

  if( m_pKeyboard->isKeyDown(KC_ESCAPE) || m_pKeyboard->isKeyDown(KC_Q) )
    return false;

      if( m_pKeyboard->isKeyDown(KC_F) && m_TimeUntilNextToggle <= 0 )
  {
    m_StatsOn = !m_StatsOn;
    showDebugOverlay( m_StatsOn );
    m_TimeUntilNextToggle = 1;
  }

  if( m_pKeyboard->isKeyDown(KC_T) && m_TimeUntilNextToggle <= 0 )
  {
    switch(m_Filtering)
    {
    case TFO_BILINEAR:
      m_Filtering = TFO_TRILINEAR;
      m_Aniso = 1;
      break;
    case TFO_TRILINEAR:
      m_Filtering = TFO_ANISOTROPIC;
      m_Aniso = 8;
      break;
    case TFO_ANISOTROPIC:
      m_Filtering = TFO_BILINEAR;
      m_Aniso = 1;
      break;
    default: break;
    }
    MaterialManager::getSingleton().setDefaultTextureFiltering( m_Filtering );
    MaterialManager::getSingleton().setDefaultAnisotropy( m_Aniso );

    showDebugOverlay(m_StatsOn);
    m_TimeUntilNextToggle = 1;
  }

  if(m_pKeyboard->isKeyDown(KC_SYSRQ) && m_TimeUntilNextToggle <= 0)
  {
    std::ostringstream ss;
    ss << "screenshot_" << ++m_NumScreenShots << ".png";
    m_Window->writeContentsToFile(ss.str());
    m_TimeUntilNextToggle = 0.5;
    m_DebugText = "Saved: " + ss.str();
  }

  if(m_pKeyboard->isKeyDown(KC_R) && m_TimeUntilNextToggle <=0)
  {
    m_SceneDetailIndex = (m_SceneDetailIndex+1)%3 ;
    switch(m_SceneDetailIndex) {
      case 0 : m_pCamera->setPolygonMode(PM_SOLID); break;
      case 1 : m_pCamera->setPolygonMode(PM_WIREFRAME); break;
      case 2 : m_pCamera->setPolygonMode(PM_POINTS); break;
    }
    m_TimeUntilNextToggle = 0.5;
  }

  static bool displayCameraDetails = false;
  if(m_pKeyboard->isKeyDown(KC_P) && m_TimeUntilNextToggle <= 0)
  {
    displayCameraDetails = !displayCameraDetails;
    m_TimeUntilNextToggle = 0.5;
    if (!displayCameraDetails)
      m_DebugText = "";
  }

  // Print camera details
  if(displayCameraDetails)
    m_DebugText = "P: " + StringConverter::toString(m_pCamera->getDerivedPosition()) +
           " " + "O: " + StringConverter::toString(m_pCamera->getDerivedOrientation());

  // Return true to continue rendering
  return true;
}

//------------------------------------------------------------
bool EFrameListener::processUnbufferedMouseInput( const FrameEvent& evt )
{
  using namespace OIS;

  // Rotation factors, may not be used if the second mouse button is pressed
  // 2nd mouse button - slide, otherwise rotate
  const MouseState &ms = m_pMouse->getMouseState();
  if( ms.buttonDown( MB_Right ) )
  {
    m_TranslateVector.x += ms.X.rel * 0.13;
    m_TranslateVector.y -= ms.Y.rel * 0.13;
  }
  else
  {
    m_RotX = Degree(-ms.X.rel * 0.13);
    m_RotY = Degree(-ms.Y.rel * 0.13);
  }

  return true;
}

//------------------------------------------------------------
bool EFrameListener::processUnbufferedJoypadInput( const FrameEvent& evt )
{
  using namespace OIS;
  const JoyStickState &js = m_pJoy->getJoyStickState();
  
  if( js.mPOV[0].direction )
  {
    EVector vCamDir   = m_pCamera->getDirection();
    vCamDir.y = 0.f;
    EVector vCamRight = m_pCamera->getRight();
    EVector vDirection = EVector::ZERO;
    if( js.mPOV[0].direction & Pov::North )
      vDirection += vCamDir;
    else if( js.mPOV[0].direction & Pov::South )
      vDirection -= vCamDir;
    if( js.mPOV[0].direction & Pov::West )
      vDirection -= vCamRight;
    else if( js.mPOV[0].direction & Pov::East )
      vDirection += vCamRight;
    vDirection.normalise();
    g_pGame->getPlayerCharacter()->move( vDirection, 0.5f );
  }
  /*if( js.mAxes[0].rel != 0 || js.mAxes[ 1 ].rel != 0 || js.mAxes[ 2 ].rel != 0)
  {
    EVector vDirection = EVector(1.f, 0.f, 0.f);
    g_pGame->getPlayerCharacter()->move( vDirection, 0.5f );
  }*/
  else
  {
    g_pGame->getPlayerCharacter()->stopMoving();
  }
  
  return true;
}

//------------------------------------------------------------
void EFrameListener::moveCamera()
{
  // Make all the changes to the camera
  // Note that YAW direction is around a fixed axis (freelook style) rather than a natural YAW
  //(e.g. airplane)
  m_pCamera->yaw(m_RotX);
  m_pCamera->pitch(m_RotY);
  m_pCamera->moveRelative(m_TranslateVector);
}

//------------------------------------------------------------
void EFrameListener::showDebugOverlay(bool bShow)
{
  if (m_DebugOverlay)
  {
    if (bShow)
      m_DebugOverlay->show();
    else
      m_DebugOverlay->hide();
  }
}

//------------------------------------------------------------
bool EFrameListener::frameStarted( const FrameEvent& evt )
{
  using namespace OIS;

  if(m_Window->isClosed())	return false;

  //Need to capture/update each device
  m_pKeyboard->capture();
  m_pMouse->capture();
  if( m_pJoy != NULL ) m_pJoy->capture();

  bool buffJ = (m_pJoy) ? m_pJoy->buffered() : true;

  //Check if one of the devices is not buffered
  if( !m_pMouse->buffered() || !m_pKeyboard->buffered() || !buffJ )
  {
    // one of the input modes is immediate, so setup what is needed for immediate movement
    if (m_TimeUntilNextToggle >= 0)
      m_TimeUntilNextToggle -= evt.timeSinceLastFrame;

    // Move about 100 units per second
    m_MoveScale   = m_MoveSpeed * evt.timeSinceLastFrame;
    // Take about 10 seconds for full rotation
    m_RotScale    = m_RotateSpeed * evt.timeSinceLastFrame;

    m_RotX = 0;
    m_RotY = 0;
    m_TranslateVector = Ogre::Vector3::ZERO;
  }

  //Check to see which device is not buffered, and handle it
  if( !m_pKeyboard->buffered() )
    if( processUnbufferedKeyInput(evt) == false )
      return false;
  if( !m_pMouse->buffered() )
    if( processUnbufferedMouseInput(evt) == false )
      return false;

  if( m_pJoy != NULL )
    //if( !m_pJoy->buffered() )
      if( processUnbufferedJoypadInput(evt) == false )
        return false;

  if( !m_pMouse->buffered() || !m_pKeyboard->buffered() || !buffJ )
    moveCamera();

  g_pGame->update( evt.timeSinceLastFrame );

  return true;
}

//------------------------------------------------------------
bool EFrameListener::frameEnded( const FrameEvent& evt )
{
  updateStats();
  return true;
}

} // end namespace engine
